package com.classLoader;


import com.hotClass.Singleton;

import java.io.*;
import java.lang.reflect.InvocationTargetException;

/**
 * 自定义加载器实现类的热部署 简单案例
 */
public class HotCLassLoaderTest {

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//        HotClassLoader hotClassLoader = new HotClassLoader();
//        Class testClass = hotClassLoader.loadClass("com.hotClass.Singleton");
        //由于HotCLassLoaderTest类是由AppCLassLoader加载的，因此只要这里显式引用到了Singleton类，并且AppCLassLoader可以加载到它，那么A就会加载，
        // 这就与HotClassLoader加载的类对象冲突了，H加载的类的实例对象不能强转为A加载的类的实例对象，故下面这样写会报错
//        Object test = testClass.newInstance();
//        Singleton singleton = (Singleton) aClass.getMethod("getInstance").invoke(test);
        Singleton.HotClassMonitor.startMonitoring();
        new Thread(() -> {
            while (true) {
                try {
                    //热加载之后，这里实际上是A加载器加载的类对象里存储的H加载器加载的类对象的实例对象singleObject
                    Object singleObject = Singleton.getInstance();
                    System.out.println(singleObject.getClass().getClassLoader());
                    singleObject.getClass().getMethod("testPrint").invoke(singleObject);
                } catch (IllegalAccessException | InstantiationException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }).start();
    }

    /**
     * jvm规定相同类对象（1.类全名相同，2.加载它的加载器实例对象相同）不能重复加载
     * 故热加载时需要new一个新的HotClassLoader实例，不能使用单例
     */
    public static class HotClassLoader extends ClassLoader{

        public final static String classFilePath = "D:\\DownLoadProject\\LearnJVM\\out\\production\\LearnJVM\\"; //编译后classpath路径

        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            return loadClass(name, false);
        }

        /**
         * 重写loadClass方法，拦截指定类 不委托给父类加载器处理
         */
        @Override
        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
            //注意：loadClass也会加载name类中引用到的相关的类，例如其父类Object，而Object类需要委托加载，故这里要加个判断
            if (name.startsWith("com.hotClass")) {
                synchronized (getClassLoadingLock(name)) {
                    //findLoadedClass：若存在name相同并且加载器实例对象相同的类对象 则将其返回，否则返回null
                    Class<?> c = findLoadedClass(name);
                    if (c == null) {
                        c = findClass(name);
                    }
                    if (resolve) {
                        resolveClass(c);
                    }
                    return c;
                }
            }
            return super.loadClass(name, resolve);
        }

        @Override
        protected Class<?> findClass(String classCompleteName) throws ClassNotFoundException {
            System.out.println("HotClassLoader尝试加载"+classCompleteName);
            byte[] result = getClassFromCustomPath(classFilePath +classCompleteName.replace(".", File.separator)+".class");
            if (result == null) {
                throw new ClassNotFoundException(classCompleteName);
            } else {
                return defineClass(classCompleteName, result, 0, result.length);
            }
        }

        private byte[] getClassFromCustomPath(String classFilePath) {
            File f = new File(classFilePath);
            InputStream is = null;
            try {
                //读取编译后的文件
                is = new FileInputStream(f);
                byte[] bytes=new byte[is.available()];
                is.read(bytes);
                return bytes;
            } catch (Exception e){

            } finally {
                try {
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }
}
